package taskhandler

import (
	"archive/zip"
	"bytes"
	"cvevulner/common"
	"cvevulner/models"
	"encoding/base64"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"gopkg.in/gomail.v2"
	"io"
	"io/ioutil"
	"mime"
	"net/smtp"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

// define email interface, and implemented auth and send method
type Mail interface {
	Auth()
	Send(message Message) error
}

type SendMail struct {
	user     string
	password string
	host     string
	port     string
	auth     smtp.Auth
}

type Attachment struct {
	name        string
	contentType string
	withFile    bool
}

type Message struct {
	from        string
	to          []string
	cc          []string
	bcc         []string
	subject     string
	body        string
	contentType string
	attachment  Attachment
}

// srcFile could be a single file or a directory
// ZipFiles compresses one or many files into a single zip archive file.
func ZipFiles(filename string, files []string, oldform, newform string) error {
	newZipFile, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer newZipFile.Close()
	zipWriter := zip.NewWriter(newZipFile)
	defer zipWriter.Close()
	for _, file := range files {
		fisExist, _ := PathExists(file)
		if !fisExist {
			logs.Error("ZipFiles, not exist, file: ", file)
			continue
		}
		zipfile, err := os.Open(file)
		if err != nil {
			return err
		}
		defer zipfile.Close()
		info, err := zipfile.Stat()
		if err != nil {
			return err
		}
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}
		header.Name = strings.Replace(file, oldform, newform, -1)
		header.Method = zip.Deflate
		writer, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}
		if _, err = io.Copy(writer, zipfile); err != nil {
			return err
		}
	}
	return nil
}

func SendEmail(attchStr string, flag int, cBody, subject string) error {
	//var mail Mail
	//emailName := beego.AppConfig.String("email::email_name")
	//emailPwd := beego.AppConfig.String("email::email_pwd")
	//emailHost := beego.AppConfig.String("email::email_host")
	//emailPort := beego.AppConfig.String("email::email_port")
	//emailPwd = ""
	elt := models.EmailList{EmailType: 1}
	el, eltErr := elt.Read("EmailType")
	if eltErr != nil {
		logs.Error("Failed to get mailing list, err: ", eltErr)
		return eltErr
	}
	toEmailName := []string{}
	for _, em := range el {
		toEmailName = append(toEmailName, em.EmailName)
	}
	//_, attchName := filepath.Split(attchStr)
	emailError := error(nil)
	//mail = &SendMail{user: emailName, password: emailPwd, host: emailHost, port: emailPort}
	if flag == 1 {
		//message := Message{from: emailName,
		//	to:      toEmailName,
		//	cc:      []string{},
		//	bcc:     []string{},
		//	subject: "The file name of the cvrf format currently to be manually reviewed is as follows.date: " + common.GetCurTime(),
		//	body: fmt.Sprintf("hi all: \r\n The list of cvrf format files that have been " +
		//		"uploaded to the file server is as follows: \r\n" + strings.ReplaceAll(attchStr, "download/", "")),
		//	contentType: "text/plain;charset=utf-8",
		//	attachment: Attachment{
		//		name:        attchStr,
		//		contentType: "text/plain",
		//		withFile:    true,
		//	},
		//}
		//emailError = mail.Send(message)
		//if emailError == nil {
		//	logs.Info("Notify cvrf that the email was sent successfully!")
		//} else {
		//	logs.Error("Notify cvrf mail delivery failure!")
		//}
		//
		subject := "The file name of the cvrf format currently to be manually reviewed is as follows.date: " + common.GetCurTime()
		body := fmt.Sprintf("hi all: \r\n The list of cvrf format files that have been " +
			"uploaded to the file server is as follows: \r\n" + strings.ReplaceAll(attchStr, "download/", ""))
		emailCc := []string{}
		sendErr := SendCommonMail(toEmailName, emailCc, subject, body, attchStr)
		if sendErr == nil {
			logs.Info("Notify cvrf that the email was sent successfully!")
		} else {
			logs.Error("Notify cvrf mail delivery failure!")
		}
	} else if flag == 2 {
		ism, queryErr := models.QueryIssueStatisticEmail()
		if len(ism) > 0 {
			toEmail := make([]string, 0)
			ccEmail := make([]string, 0)
			for _, im := range ism {
				if im.EmailType == 1 {
					toEmail = append(toEmail, im.EmailName)
				} else {
					ccEmail = append(ccEmail, im.EmailName)
				}
			}
			curDate := common.GetCurDateFormat()
			//message := Message{from: emailName,
			//	to:          toEmail,
			//	cc:          ccEmail,
			//	bcc:         []string{},
			//	subject:     "【" + curDate + " CVE进展】openEuler需解决CVE列表，请各接口人尽快安排解决",
			//	body:        cBody,
			//	contentType: "text/plain;charset=utf-8",
			//	attachment: Attachment{
			//		name:        attchStr,
			//		contentType: "text/plain",
			//		withFile:    true,
			//	},
			//}
			//emailError = mail.Send(message)
			//if emailError == nil {
			//	logs.Info("Notify issue statistics that the email was sent successfully!")
			//} else {
			//	logs.Error("Notify issue statistics mail delivery failure!")
			//}
			subject := "【" + curDate + " CVE进展】openEuler需解决CVE列表，请各接口人尽快安排解决"
			body := cBody
			sendErr := SendCommonMail(toEmail, ccEmail, subject, body, attchStr)
			if sendErr == nil {
				logs.Info("Notify cvrf that the email was sent successfully!")
			} else {
				logs.Error("Notify cvrf mail delivery failure!")
			}
		} else {
			emailError = queryErr
		}
	} else if flag == 3 {
		//message := Message{from: emailName,
		//	to:          toEmailName,
		//	cc:          []string{},
		//	bcc:         []string{},
		//	subject:     subject,
		//	body:        cBody,
		//	contentType: "text/plain;charset=utf-8",
		//	attachment: Attachment{
		//		name:        attchStr,
		//		contentType: "text/plain",
		//		withFile:    true,
		//	},
		//}
		//emailError = mail.Send(message)
		//if emailError == nil {
		//	logs.Info("Notify cve that the email was sent successfully!")
		//} else {
		//	logs.Error("Notify cve mail delivery failure!")
		//}
		subject := subject
		body := cBody
		sendErr := SendCommonMail(toEmailName, []string{}, subject, body, attchStr)
		if sendErr == nil {
			logs.Info("Notify cvrf that the email was sent successfully!")
		} else {
			logs.Error("Notify cvrf mail delivery failure!")
		}
	} else {
		//message := Message{from: emailName,
		//	to:          toEmailName,
		//	cc:          []string{},
		//	bcc:         []string{},
		//	subject:     "updateinfo.xml and the excel file corresponding to SA",
		//	body:        "hi all: \r\n The attachment is: updateinfo.xml and the excel file corresponding to SA.",
		//	contentType: "text/plain;charset=utf-8",
		//	attachment: Attachment{
		//		name:        attchStr,
		//		contentType: "text/plain",
		//		withFile:    true,
		//	},
		//}
		//emailError = mail.Send(message)
		//if emailError == nil {
		//	logs.Info("updateinfo.xml and the excel file corresponding to SA, Mail sent successfully!")
		//} else {
		//	logs.Error("updateinfo.xml and the excel file corresponding to SA, Mail sent failure!")
		//}
		subject := "updateinfo.xml and the excel file corresponding to SA"
		body := "hi all: \r\n The attachment is: updateinfo.xml and the excel file corresponding to SA."
		sendErr := SendCommonMail(toEmailName, []string{}, subject, body, attchStr)
		if sendErr == nil {
			logs.Info("Notify cvrf that the email was sent successfully!")
		} else {
			logs.Error("Notify cvrf mail delivery failure!")
		}
	}
	return emailError
}

func (mail *SendMail) Auth() {
	mail.auth = smtp.PlainAuth("", mail.user, mail.password, mail.host)
}

func (mail SendMail) Send(message Message) error {
	mail.Auth()
	buffer := bytes.NewBuffer(nil)
	boundary := "GoBoundary"
	Header := make(map[string]string)
	//Header["From"] = message.from
	Header["From"] = "cve-manager" + "<" + message.from + ">"
	Header["To"] = strings.Join(message.to, ";")
	Header["Cc"] = strings.Join(message.cc, ";")
	Header["Bcc"] = strings.Join(message.bcc, ";")
	Header["Subject"] = message.subject
	Header["Content-Type"] = "multipart/mixed;boundary=" + boundary
	Header["Mime-Version"] = "1.0"
	Header["Date"] = time.Now().String()
	mail.writeHeader(buffer, Header)
	body := "\r\n--" + boundary + "\r\n"
	body += "Content-Type:" + message.contentType + "\r\n"
	body += "\r\n" + message.body + "\r\n"
	buffer.WriteString(body)
	if message.attachment.withFile {
		attachment := "\r\n--" + boundary + "\r\n"
		attachment += "Content-Transfer-Encoding:base64\r\n"
		attachment += "Content-Disposition:attachment\r\n"
		attachment += "Content-Type:" + message.attachment.contentType + ";name=\"" +
			strings.Replace(message.attachment.name, "download/", "", -1) + "\"\r\n"
		buffer.WriteString(attachment)
		defer func() {
			if err := recover(); err != nil {
				logs.Error(err)
			}
		}()
		mail.writeFile(buffer, message.attachment.name)
	}
	buffer.WriteString("\r\n--" + boundary + "--")
	toSend := make([]string, 0)
	toSend = append(toSend, message.to...)
	toSend = append(toSend, message.cc...)
	header := smtp.SendMail(mail.host+":"+mail.port, mail.auth, message.from, toSend, buffer.Bytes())
	logs.Info("header: ", header)
	return nil
}

func (mail SendMail) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
	header := ""
	for key, value := range Header {
		header += key + ":" + value + "\r\n"
	}
	header += "\r\n"
	buffer.WriteString(header)
	return header
}

// read and write the file to buffer
func (mail SendMail) writeFile(buffer *bytes.Buffer, fileName string) {
	file, err := ioutil.ReadFile(fileName)
	if err != nil {
		panic(err.Error())
	}
	payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
	base64.StdEncoding.Encode(payload, file)
	buffer.WriteString("\r\n")
	for index, line := 0, len(payload); index < line; index++ {
		buffer.WriteByte(payload[index])
		if (index+1)%76 == 0 {
			buffer.WriteString("\r\n")
		}
	}
}

func SendCommonMail(mailTo, mailCc []string, subject string, body, attchName string) error {
	emailName := beego.AppConfig.String("email::email_name")
	emailPwd := beego.AppConfig.String("email::email_pwd")
	emailHost := beego.AppConfig.String("email::email_host")
	emailPwd = ""
	emailPort := beego.AppConfig.String("email::email_port")
	mailConn := map[string]string{
		"user": emailName,
		"pass": emailPwd,
		"host": emailHost,
		"port": emailPort,
	}
	port, _ := strconv.Atoi(mailConn["port"])
	m := gomail.NewMessage()
	m.SetHeader("From", "cve-manager"+"<"+mailConn["user"]+">")
	m.SetHeader("To", mailTo...)
	m.SetHeader("Cc", mailCc...)
	m.SetHeader("Subject", subject)
	m.SetBody("text/plain", body)
	//m.Attach(attchName)
	_, fileName := filepath.Split(attchName)
	m.Attach(attchName,
		gomail.Rename(fileName),
		gomail.SetHeader(map[string][]string{
			"Content-Disposition": []string{
				fmt.Sprintf(`attachment; filename="%s"`, mime.QEncoding.Encode("UTF-8", fileName)),
			},
		}),
	)
	d := gomail.NewDialer(mailConn["host"], port, mailConn["user"], mailConn["pass"])
	err := d.DialAndSend(m)
	return err
}

func SendCvrfEmail(subFileSlice []string) {
	elt := models.EmailList{EmailType: 1}
	el, eltErr := elt.Read("EmailType")
	if eltErr != nil {
		logs.Error("Failed to get mailing list, err: ", eltErr)
	}
	toEmailName := []string{}
	for _, em := range el {
		toEmailName = append(toEmailName, em.EmailName)
	}
	subject := "The file name of the cvrf format currently to be manually reviewed is as follows.date: " + common.GetCurTime()
	fileStr := strings.Join(subFileSlice, "\r\n")
	body := fmt.Sprintf("hi all: \r\n The list of cvrf format files that have been uploaded to the file server is as follows: \r\n" + fileStr)
	sendErr := SendCommonMail(toEmailName, []string{}, subject, body, "")
	if sendErr == nil {
		logs.Info("Notify cvrf that the email was sent successfully!")
	} else {
		logs.Error("Notify cvrf mail delivery failure!")
	}
}
